﻿using ELab.Communication;
using ELab.Communication.Events;
using ELab.Organization;
using ELab.Organization.Routing;
using ELab.Settings;
using ELab.Threading;
using System;
using System.Linq;
using System.Collections.Generic;
using ELab.Communication.Messages;

namespace ELab
{
    /// <summary>
    /// Represents a ELab server. This is the main class to construct and run a ELab server.
    /// </summary>
    public class ELabServer : IRunnable
    {
        #region Ppublic properties & Events
        public CommunicatorConnectedHandler OnClientConnected;
        public CommunicatorDisconnectedHandler OnClientDisconnected;
        public MessageReceivedFromRemoteApplicationHandler OnMessageReceived;

        public SortedList<string, ELabClientApplication> Applications
        {
            get
            {
                return _clientApplicationList.Applications;
            }
        }
        #endregion

        /// <summary>
        /// Settings.
        /// </summary>
        private readonly ELabSettings _settings;

        /// <summary>
        /// Routing table.
        /// </summary>
        private readonly RoutingTable _routingTable;

        /// <summary>
        /// A Graph consist of server nodes. It also holds references to ELabAdjacentServer objects.
        /// </summary>
        private readonly ELabServerGraph _serverGraph;

        /// <summary>
        /// Reference to all ELab Managers. It contains communicators to all instances of ELab manager.
        /// </summary>
        private readonly OrganizationController _ELabManager;

        /// <summary>
        /// List of applications
        /// </summary>
        private readonly ELabClientApplicationList _clientApplicationList;

        /// <summary>
        /// Communication layer.
        /// </summary>
        private readonly CommunicationLayer _communicationLayer;

        /// <summary>
        /// Organization layer.
        /// </summary>
        private readonly OrganizationLayer _organizationLayer;

        /// <summary>
        /// Constructor.
        /// </summary>
        public ELabServer()
        {
            _settings = ELabSettings.Instance;
            _serverGraph = new ELabServerGraph();
            _clientApplicationList = new ELabClientApplicationList();
            _ELabManager = new OrganizationController("ELabController");
            _routingTable = new RoutingTable();
            _communicationLayer = new CommunicationLayer();
            _organizationLayer = new OrganizationLayer(_communicationLayer, _routingTable, _serverGraph, ref _clientApplicationList, _ELabManager);
            _ELabManager.OrganizationLayer = _organizationLayer;
        }

        /// <summary>
        /// Starts the ELab server.
        /// </summary>
        public void Start()
        {
            _communicationLayer.Start();
            _organizationLayer.Start();

            if (_clientApplicationList.Applications == null || _clientApplicationList.Applications.Values == null) return;
            foreach (var app in _clientApplicationList.Applications.Values)
            {
                app.CommunicatorConnected += ELabServer_CommunicatorConnected;
                app.CommunicatorDisconnected += ELabServer_CommunicatorDisconnected;
                app.MessageReceived += ELabServer_MessageReceived;
            }
        }

        private void ELabServer_CommunicatorDisconnected(object sender, CommunicatorDisconnectedEventArgs e)
        {
            if (OnClientDisconnected != null) OnClientDisconnected(sender, e);
        }

        private void ELabServer_CommunicatorConnected(object sender, Communication.Events.CommunicatorConnectedEventArgs e)
        {
            if (OnClientConnected != null) OnClientConnected(sender, e);
        }

        private void ELabServer_MessageReceived(object sender, Communication.Events.MessageReceivedFromRemoteApplicationEventArgs e)
        {
            if (OnMessageReceived != null) OnMessageReceived(sender, e);
        }



        /// <summary>
        /// Stops the ELab server.
        /// </summary>
        /// <param name="waitToStop">True, if caller thread must be blocked until ELab server stops.</param>
        public void Stop(bool waitToStop)
        {
            _communicationLayer.Stop(waitToStop);
            _organizationLayer.Stop(waitToStop);
        }

        /// <summary>
        /// Waits stopping of ELab server.
        /// </summary>
        public void WaitToStop()
        {
            _communicationLayer.WaitToStop();
            _organizationLayer.WaitToStop();
        }

        /// <summary>
        /// Can send message from server to clients
        /// </summary>
        /// <param name="applicationName"></param>
        /// <param name="mesageData"></param>
        private void SendToClient(byte[] mesageData, string applicationName = null)
        {
            if (!string.IsNullOrEmpty(applicationName))
            {
                ELabDataTransferMessage mess = new ELabDataTransferMessage
                {
                    DestinationApplicationName = applicationName,
                    TransmitRule = MessageTransmitRules.NonPersistent,
                    MessageData = mesageData
                };
                _ELabManager.SendMessage(mess);
            }
            else
            {
                foreach (var app in _clientApplicationList.Applications.Values)
                {
                    if (app.ConnectedCommunicatorCount <= 0) continue;
                    ELabDataTransferMessage mess = new ELabDataTransferMessage
                    {
                        DestinationApplicationName = app.Name,
                        TransmitRule = MessageTransmitRules.NonPersistent,
                        MessageData = mesageData
                    };
                    _ELabManager.SendMessage(mess);
                }
            }
        }
    }
}
